#coding: utf-8

import sys,re,string
from optparse import OptionParser

EXCEPTS=[IOError]

import tybc_compile2
EXCEPTS.append(tybc_compile2.CompileError)

try: 
  import tybc_compile
  OLD_EXIST=True
  EXCEPTS.append(tybc_compile.CompileError)
except:
  OLD_EXIST=False

class LexError(Exception):
  pass
EXCEPTS.append(LexError)

class AsmError(Exception):
  pass
EXCEPTS.append(AsmError)

def lex(file):
  tok=[]
  line=0
  lower_digits=string.ascii_lowercase+string.digits

  for l in open(file):
    line+=1
    a=[]
    s=l.rstrip()
    while True:
      s=s.lstrip()
      if len(s)==0 or s[0]=='#':
        break

      if s[0] in string.ascii_lowercase:
        i=1
        while i<len(s) and s[i] in lower_digits:
          i+=1
        g=s[:i]
        s=s[i:]
        if g=='function':
          a.append(('FUNCTION',None,line))
        elif g=='write':
          a.append(('WRITE',None,line))
        elif g=='read':
          a.append(('READ',None,line))
        elif g=='while':
          a.append(('WHILE',None,line))
        elif g=='if':
          a.append(('IF',None,line))
        elif g=='else':
          a.append(('ELSE',None,line))
        elif g=='elf':
          a.append(('ELSIF',None,line))
        elif g=='end':
          a.append(('END',None,line))
        elif g=='array':
          a.append(('ARRAY',None,line))
        elif g=='et':
          a.append(('ET',None,line))
        elif g=='ou':
          a.append(('OU',None,line))
        elif g=='non':
          a.append(('NON',None,line))
        elif g=='goto':
          a.append(('GOTO',None,line))
        elif g=='label':
          a.append(('LABEL',None,line))
        elif g=='vrai':
          a.append(('VRAI',None,line))
        elif g=='faux':
          a.append(('FAUX',None,line))
        else:
          a.append(('ID',g,line))
        continue

      i=0
      while i<len(s) and s[i] in string.digits:
        i+=1
      if i>0:
        a.append(('NUM',int(s[:i]),line))
        s=s[i:]
        continue

      if s[:2]==':=':
        s=s[2:]
        a.append(('DEF',None,line))
      elif s[:2]=='><':
        s=s[2:]
        a.append(('NE',None,line))
      elif s[:2]=='>=':
        s=s[2:]
        a.append(('GE',None,line))
      elif s[:2]=='<=':
        s=s[2:]
        a.append(('LE',None,line))
      elif s[0]=='=':
        s=s[1:]
        a.append(('EQ',None,line))
      elif s[0]=='>':
        s=s[1:]
        a.append(('GT',None,line))
      elif s[0]=='<':
        s=s[1:]
        a.append(('LT',None,line))
      elif s[0]=='+':
        s=s[1:]
        a.append(('PLUS',None,line))
      elif s[0]=='-':
        s=s[1:]
        a.append(('MINUS',None,line))
      elif s[0]=='*':
        s=s[1:]
        a.append(('MUL',None,line))
      elif s[0]=='/':
        s=s[1:]
        a.append(('DIV',None,line))
      elif s[0]=='%':
        s=s[1:]
        a.append(('MOD',None,line))
      elif s[0]==';':
        s=s[1:]
        a.append(('SEMI',None,line))
      elif s[0]==',':
        s=s[1:]
        a.append(('COMMA',None,line))
      elif s[0]=='$':
        s=s[1:]
        a.append(('DOLLAR',None,line))
      elif s[0]=='(':
        s=s[1:]
        a.append(('LPAR',None,line))
      elif s[0]==')':
        s=s[1:]
        a.append(('RPAR',None,line))
      elif s[0]=='[':
        s=s[1:]
        a.append(('LBRA',None,line))
      elif s[0]==']':
        s=s[1:]
        a.append(('RBRA',None,line))
      elif s[0]=='|':
        s=s[1:]
        a.append(('PIPE',None,line))
      elif s[0]=='{':
        s=s[1:]
        a.append(('LBLO',None,line))
      elif s[0]=='}':
        s=s[1:]
        a.append(('RBLO',None,line))
      else:
        raise LexError('unknown token at "%s" (line %s)' % (s, line))

    if len(a)!=0:
      tok.extend(a)
      tok.append(('NL',None,line))

  return tok

def asm(code,mac):
  for i,a in enumerate(code):
    if isinstance(a, (int,long,str)):
      pass
    elif isinstance(a,list) and len(a)==2 and \
         isinstance(a[0], str) and isinstance(a[1], (int,long,str)):
      pass
    else:
      raise AsmError('code is invalid : (%s : %s)' % (a,i))

  for k,v in mac.iteritems():
    if not (isinstance(k, str) and re.match('[A-KM-Z]\S*\Z',k)):
      raise AsmError('macro name error (%s : %s)' % (k,v))
    if not isinstance(v, (int,long)):
      raise AsmError('macro value must be int (%s : %s)' % (k,v))

  out=[]
  lt=set()
  for u in code:
    if not isinstance(u,list):
      out.append(u)
    elif u[0]=='sal':
      p = mac[u[1]] if u[1] in mac else u[1]
      if not (isinstance(p, (int,long)) and p>=0):
        raise AsmError('cannot expand %s' % u)
      out.extend([0]*p)
    elif u[0]=='pop':
      p = mac[u[1]] if u[1] in mac else u[1]
      if not (isinstance(p, (int,long)) and p>=0):
        raise AsmError('cannot expand %s' % u)
      out.extend(['pop']*p)
    elif u[0]=='god':
      out.append(u[1])
      out.append('god')
    elif u[0]=='gto':
      out.append(u[1])
      out.append('gto')
    elif u[0]=='jpo':
      out.append(u[1])
      out.append('jpo')
    elif u[0]=='jmp':
      out.append(1)
      out.append(u[1])
      out.append('jpo')
    elif u[0]=='hal':
      out.append(u[1])
      out.append('hal')
    elif u[0]=='lab':
      if not (isinstance(u[1], str) and re.match('L\S*\Z',u[1])):
        raise AsmError("label name error : "+u[1])
      if u[1] in lt:
        raise AsmError("label %s is already appeared" % u[1])
      lt.add(u[1])
      out.append(u)
    else:
      raise AsmError('cannot expand %s' % u)

  for i in range(len(out)):
    if isinstance(out[i], str) and out[i] in mac:
      out[i]=mac[out[i]]

  def opt(out):
    ret=out[:]
    while True:
      ln=len(ret)
      rep={}
      j=None
      for i in range(len(ret)):
        if isinstance(ret[i],list) and ret[i][0]=='lab':
          if j==None:
            j=ret[i][1]
          else:
            rep[ret[i][1]]=j
            ret[i]=None
        else:
          j=None
      ret=[i for i in ret if i!=None]

      for i in range(len(ret)):
        if isinstance(ret[i], str) and ret[i] in rep:
          ret[i]=rep[ret[i]]

      for i in range(3,len(ret)):
        if ret[i-3]==1 and ['lab',ret[i-2]]==ret[i] and ret[i-1]=='jpo':
          ret[i-3]=ret[i-2]=ret[i-1]=None
      ret=[i for i in ret if i!=None]

      if ln==len(ret):
        return ret

  out=opt(out)

  lab={}
  j=0
  for i in range(len(out)):
    if isinstance(out[i],list) and out[i][0]=='lab':
      lab[out[i][1]]=j
      out[i]=None
    else:
      j+=1
  out=[i for i in out if i!=None]

  for i in range(len(out)):
    if out[i] in lab:
      out[i]=lab[out[i]]
  return out

usage='usage: python %prog source [options]'
op=OptionParser(usage)
op.set_defaults(fout='epro.code')
op.add_option('-o',dest='fout',metavar='file',help='output file')
op.add_option('-a',default=True,action='store_false',dest='achk',help='not check array index')
if OLD_EXIST: op.add_option('-v',default=True,action='store_false',dest='new',help='use old compiler')
op.add_option('-t',default=False,action='store_true',dest='tape',help='use tape')
(opts,args)=op.parse_args()
if len(args)!=1:
  op.error('too many or few arguments')
if opts.tape and not opts.new:
  op.error("old compiler can't handle tape")

try:
  tok=lex(args[0])
  code,mac=tybc_compile2.compile(tok,opts.achk,opts.tape) if opts.new else tybc_compile.compile(tok,opts.achk)
  prog=asm(code,mac)
  f=open(opts.fout,'w')
  for l in prog:
    f.write(str(l)+'\n')
  f.close()
except tuple(EXCEPTS), e:
  sys.exit(e)
